/** Fichier qui implémente les classes représentant les vecteurs et vecteurs
 * unitaires de l'espace.
 *
 * Ce fichier contient l'implémentation des classes Vecteur et VecteurU.
 *
 * @file Vecteur.cpp
 * @author Jean-Marc Comby + Romain Dequesne
 * @author Johan "Solo" Luisier
 *
 * @date 07 Avril 2004
 */
#include "Vecteur.hpp"
#include <cmath>

using namespace std;

/** Constructeur par défaut.
 *
 * Constructeur usuel, construit un Vecteur à partir de ses trois
 * composantes.
 *
 * La version par défaut construit le vecteur nul.
 *
 * @param[in] x coordonnées selon Ox.
 * @param[in] y coordonnées selon Oy.
 * @param[in] z coordonnées selon Oz.
 */
Vecteur::Vecteur( const double& x, const double& y, const double& z )
    : Coord( x, y, z )
{}

/** Constructeur permettant de construire le vecteur reliant deux points.
 *
 * Ce constructeur permet de construire un Vecteur à partir de son origine et de
 * son extrémité.
 *
 * @param[in] a origine du Vecteur.
 * @param[in] b extrémité du Vecteur.
 */
Vecteur::Vecteur( const Coord& a, const Coord& b )
    : Coord( b.x() - a.x(), b.y() - a.y(), b.z() - a.z() )
{}

/** Constructeur permettant de promouvoir un Coord en Vecteur.
 *
 * @param[in] v instance de Coord à promouvoir.
 */
Vecteur::Vecteur( const Coord& v )
    : Coord( v )
{}

/** Constructeur de copie, permettant de dupliquer un Vecteur.
 *
 * @param[in] v instance de Vecteur à copier.
 */
Vecteur::Vecteur( const Vecteur& v )
    : Coord( v )
{}

/** Constructeur permettant de transformer un VecteurU en Vecteur.
 *
 * Ce constructeur permet de transformer un VecteurU en Vecteur. La
 * nouvelle instance est unitaire, mais on peut ensuite la multiplier
 * par un scalaire.
 *
 * @param[in] v instance de VecteurU à transformer.
 */
Vecteur::Vecteur( const VecteurU& v )
    : Coord( v )
{}

/** Destructeur, virtuel pour assurer une séquence de destruction correcte.
 *
 * Le destructeur n'a rien de spécial à faire.
 */
Vecteur::~Vecteur()
{}

/** Opérateur de promotion d'un objet de type VecteurBase en Vecteur.
 *
 * Affectation et promotion d'un objet de type vector< double > en Vecteur.
 *
 * @param[in] vecBoost
 *
 * @return une référence sur l'instance courante.
 */
Vecteur& Vecteur::operator=( const VecteurBase& vecBoost )
{
    Coord::operator=( vecBoost );

    return *this;
}

/** Constructeur permettant de promouvoir un VecteurBase.
 *
 * Constructeur de "copie", qui permet de promouvoir un objet issu du
 * résultat d'une opération en un Vecteur.
 *
 * @param[in] vecBoost objet analogue à vector< double >.
 */
Vecteur::Vecteur( const VecteurBase& vecBoost )
    : Coord( vecBoost )
{}

/** Opérateur d'addition.
 *
 * Méthode permettant d'additionner à l'insance courante un autre
 * Vecteur.
 *
 * @param[in] v vecteur à additionner.
 *
 * @return une référence constante sur l'instance courante modifiée.
 */
Vecteur& Vecteur::operator+=( const Vecteur &v )
{
    this -> VecteurBase::operator+=( v );

    return *this;
}

/** Méthode d'addition de deux instances.
 *
 * Méthode qui permet d'additionner deux instances de Vecteur. On fait
 * appel ici à la méthode += sur une copie de l'instance courante.
 *
 * @param[in] v vecteur à additionner.
 *
 * @return une nouvelle instance résultant de l'addition des deux
 * instances de Vecteur
 */
Vecteur Vecteur::operator+( const Vecteur& v ) const
{
    Vecteur tmp( *this );

    return tmp += v;
}

/** Opérateur de soustraction.
 *
 * Méthode permettant de soustraire à l'insance courante un autre
 * Vecteur.
 *
 * @param[in] v vecteur à soustraire.
 *
 * @return une référence constante sur l'instance courante modifiée.
 */
Vecteur &Vecteur::operator-=( const Vecteur& v )
{
    this -> VecteurBase::operator-=( v );

    return *this;
}

/** Méthode de soustraction de deux instances.
 *
 * Méthode qui permet de soustraire deux instances de Vecteur. On fait
 * appel ici à la méthode -= sur une copie de l'instance courante.
 *
 * @param[in] v vecteur à additionner.
 *
 * @return une nouvelle instance résultant de l'addition des deux
 * instances de Vecteur
 */
Vecteur Vecteur::operator-( const Vecteur& v ) const
{
    Vecteur tmp( *this );

    return tmp -= v;
}

/** Opérateur de multiplication par un scalaire.
 *
 * Méthode permettant de multiplier l'instance courante par un
 * scalaire.
 *
 * @param[in] a nombre réel par lequel on veut multiplier l'instance
 * courante.
 *
 * @return une référence constante sur l'instance courante modifiée.
 */
Vecteur& Vecteur::operator*=( const double& a )
{
    this -> VecteurBase::operator*=( a );

    return *this;
}

/** Méthode de multiplication par un scalaire.
 *
 * Permet d'obtenir le résultat de la multiplication d'un Vecteur par
 * un scalaire. On utilise la méthode *= sur une copie de l'instance
 * courante.
 *
 * @param[in] a nombre réel par lequel on veut multiplier l'instance
 * courante.
 *
 * @return une nouvelle instance résultant de la multiplication de
 * l'instance courante par a.
 */
Vecteur Vecteur::operator*( const double& a ) const
{
    Vecteur tmp( *this );

    return tmp *= a;
}

/** Fonction de multiplication par un scalaire.
 *
 * Fonction permettant de pouvoir écrire indifféremment \f$ a \cdot
 * \vec{v} \f$ ou \f$\vec{v} \cdot a \f$.
 *
 * @param[in] a nombre réel par lequel on veut multiplier l'instance de
 * Vecteur.
 * @param[in] v Vecteur que l'on veut multiplier.
 *
 * @return une nouvelle instance résultant de la multiplication de v
 * par a.
 */
Vecteur operator*( const double& a, const Vecteur& v )
{
    return v * a;
}

/** Opérateur d'inversion spatiale.
 *
 * Méthode permettant de réaliser une inversion de l'espace
 * (parité). On utilise la multiplication par -1.
 *
 * @return une nouvelle instance de Vecteur, image de l'instance
 * courante par parité spaciale.
 */
Vecteur Vecteur::operator-() const
{
    return *this * -1.;
}

/** Opérateur de produit scalaire entre deux instances de Vecteur.
 *
 * Permet de récupérer la valeur de produit scalaire entre deux
 * instances de Vecteur.
 *
 * Fait appel à la fonction inner_prod.
 *
 * @param[in] v Vecteur dont on voudrait avoir la valeur du produit
 * scalaire avec l'instance courante.
 *
 * @return la valeur du produit scalaire de l'instance courante par \c
 * v.
 */
double Vecteur::operator*( const Vecteur& v ) const
{
    return inner_prod( *this, v );
}

/** Opérateur de produit scalaire entre une instance de Vecteur et une instance
 * de Coord.
 *
 * Permet d'obtenir le produit scalaire d'un vecteur par un point.
 *
 * Fait appel à la fonction inner_prod.
 *
 * @param[in] pt point de l'espace dont on voudrait avoir la valeur du
 * produit scalaire avec l'instance courante.
 *
 * @return la valeur du produit scalaire de l'instance courante par @c
 * pt.
 */
double Vecteur::operator*( const Coord& pt ) const
{
    return inner_prod( *this, pt );
}

double operator*( const Coord& pt, const Vecteur& vec )
{
    return vec * pt;
}

/** Méthode réalisant le produit vectoriel de deux instances de Vecteur.
 *
 * Permet d'obtenir le produit scalaire de deux instances de Vecteur.
 *
 * @param[in] v Vecteur à multiplier avec l'instance courante.
 *
 * @return une nouvelle instance résultant du produit vectoriel de
 * l'instance courante par v.
 */
Vecteur Vecteur::operator^( const Vecteur& v ) const
{
    return Vecteur( y() * v.z() - v.y() * z(),
                    z() * v.x() - v.z() * x(),
                    x() * v.y() - v.x() * y() );
}

/** Méthode calculant l'angle formé par deux instances de Vecteur.
 *
 * Permet d'obtenir l'angle entre deux instances de Vecteur. On
 * utilise le fait que \f[ \vec{u} \cdot \vec{v} = \left\vert
 * \left\vert \vec{u} \right\vert \right\vert \, \left\vert \left\vert
 * \vec{v} \right\vert \right\vert \, \cos \left( \theta \right) \f]
 *
 * Lorsque l'angle est très proche de la normale, on arrondi la valeur
 * du cosinus.
 *
 * @param[in] v Vecteur dont on veut connaître l'angle avec l'instance
 * courante.
 *
 * @return angle (en radian) entre v et l'instance courante.
 */
double Vecteur::angle( const Vecteur& v ) const
{
    double cosinus( *this * v / sqrt( v.norme() * norme() ) );

    if ( abs( cosinus ) < 1.e-7 )
        cosinus = 0.;

    return acos( cosinus );
}

/** Méthode calculant la norme (au @e carré) d'une instance de Vecteur.
 *
 * Donne accès au carré de la norme du Vecteur, en utilisant le
 * produit scalaire avec lui-même.
 *
 * @return la valeur du carré de la norme de l'instance courante.
 */
double Vecteur::norme() const
{
    return (*this) * (*this);
}

/** Méthode réalisant la projection orthogonale d'une instance sur un vecteur
 * unitaire.
 *
 * Cette méthode réalise la projection orthogonale de l'instance courante sur
 * un vecteur unitaire, si @f$ \vec{v} @f$ est l'instance courante et @f$
 * \vec{d} @f$ le vecteur unitaire, alors le résultat est @f[ \left( \vec{d}
 * \cdot \vec{d} \right) \vec{d} @f]
 *
 * @param[in] v VecteurU sur lequel on veut projeter l'instance courante.
 *
 * @return un Vecteur dont la norme est égale au produit scalaire de
 * l'instance courante par v et dont la direction est donnée par v.
 */
Vecteur Vecteur::project( const VecteurU &v ) const
{
    return ( *this * v ) * v;
}

/** Méthode réalisant l'anti projection orthogonale d'une instance sur un
 * vecteur unitaire.
 *
 * Réalise l'inverse de project, càd ici on obtient la composante qui
 * est dans le plan normal à un vecteur unitaire.
 *
 * On utilise la projection orthogonale, que l'on soustrait à
 * l'instance courante.
 *
 * @param[in] v VecteurU sur lequel on veut projeter l'instance courante.
 *
 * @return résultat de la soustraction de l'instance courante par la
 * projection orthogonale de l'instance courante sur v.
 */
Vecteur Vecteur::OrthoProject( const VecteurU &v ) const
{
    return *this - this -> project( v );
}

/** Méthode permettant de calculer la réflexion d'une instance par un axe.
 *
 * Cette méthode permet d'obtenir l'image de l'instance courante par réflexion
 * sur un axe donné. La réflexion se fait dans le plan déterminé par l'axe et
 * l'instance courante.
 *
 * On utilise la méthode OrthoProject en interne.
 *
 * @param[in] axe VecteurU autour duquel on effectue la réflexion.
 *
 * @return une nouvelle instance de Vecteur, qui est l'image de l'instance
 * courante réfléchie sur l'axe c axe.
 */
Vecteur Vecteur::reflect( const VecteurU& axe) const
{
    return *this - ( 2. * ( this -> OrthoProject( axe ) ) );
}


/** Constructeur par défaut.
 *
 */
VecteurU::VecteurU()
    : Vecteur()
{
    StatutInterne = Coord::Inconnu;
}

/**
 *
 * Constructeur standard pour VecteurU, initialise les composantes de
 * telle sorte que la direction est conservée par rapport aux
 * arguments, mais la norme est 1. La méthode transforme s'occupe de
 * normaliser le VecteurU.
 *
 * La version par défaut construit un VecteurU parrallèle à Ox.
 *
 * @param[in] x coordonnées selon Ox.
 * @param[in] y coordonnées selon Oy.
 * @param[in] z coordonnées selon Oz.
 */
VecteurU::VecteurU( const double& x, const double& y, const double& z )
    : Vecteur( x, y, z )
{
    transforme();
}

/** Constructeur permettant de construire le vecteur orienté par deux points.
 *
 * Ce constructeur permet d'initialiser un VecteurU avec une direction
 * donnée par deux points. La méthode transforme s'occupe de normaliser le
 * VecteurU.
 *
 * @param[in] a origine du VecteurU.
 * @param[in] b "extrémité" du VecteurU.
 */
VecteurU::VecteurU( const Coord& a, const Coord& b )
    : Vecteur( a, b )
{
    transforme();
}

/** Construceur permettant de promouvoir une instance de Vecteur en VecteurU.
 *
 * Ce constructeur permet de promouvoir un Vecteur en VecteurU. La
 * direction est consrevée, on change juste la norme. La méthode
 * transforme s'occupe de normaliser le VecteurU.
 *
 * @param[in] v Vecteur à promouvoir.
 */
VecteurU::VecteurU( const Vecteur& v )
    : Vecteur( v )
{
    transforme();
}

/** Constructeur permettant de promouvoir une instance de VecteurBase en
 * VecteurU.
 *
 * Constructeur de "copie", qui permet de promouvoir un objet issu du
 * résultat d'une opération en un Vecteur.
 *
 * @param[in] vecBoost objet analogue à vector< double >.
 */
VecteurU::VecteurU( const VecteurBase& vecBoost )
    : Vecteur( vecBoost )
{
    transforme();
}

/** Destructeur, virtuel pour assurer une séquence de destruction correcte.
 *
 * Le destucteur n'a rien de spécial à faire.
 */
VecteurU::~VecteurU()
{}

/** Opérateur d'affecation promouvant une instance de VecteurBase.
 *
 * Affectation et promotion d'un objet de type vector< double > en VecteurU.
 *
 * @param[in] vecBoost
 *
 * @return une référence sur l'instance courante.
 */
VecteurU& VecteurU::operator=( const VecteurBase& vecBoost )
{
    Coord::operator=( vecBoost );

    transforme();

    return *this;
}

/** Méthode qui vérifie que le vecteur soit bien unitaire.
 *
 * La méthode s'assure que la norme du VecteurU est bien de 1. Si
 * l'instance courante est égale au vecteur nul (avec une tolérance de
 * \f$ 10^{-7} \f$), on transforme l'instance en VecteurU parallèle à
 * Ox. Si la norme est déjà égale (avec la même tolérance) à 1, on ne
 * fait rien et sinon on normalise proprement.
 */
void VecteurU::transforme()
{
    if ( norme() < 1.e-7 )
    {
        operator()( 0 ) = 1.;
        operator()( 1 ) = 0.;
        operator()( 2 ) = 0.;
    }
    else if ( abs( norme() - 1. ) > 1.e-7 )
    {
        double R( sqrt( norme() ) );
        operator*=( 1. / R );
    }
}
